/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "NTree.h"
#pragma once

#include "Relations.h"
#include "Geometry/BoxTests.h"
#include "Geometry/Sphere.h"
#include "Geometry/Cylinder.h"


template<class VecType>
class SphereCon
{
public:
	SphereCon(const float& radius, VecType vPos = VecType(0)) :
		sph(Sphere<VecType>(radius, vPos)),
		size( AuxMath::log2( ((int) radius + vPos.maxComponent()) +1))
	{

	}

	const int getSize() const {return size;}

	VoxelType fn(const Box<VecType>& box) const
	{
		return relationToVoxel(sph.testBox(box));
	}
private:
	Sphere<VecType> sph;
	int size;
};

template<class VecType>
class CylinderCon
{
public:
	CylinderCon(const float radius, const float length, const int iAxis) :
		cyl(Cylinder<VecType>(radius, length, iAxis)),
		size( AuxMath::log2( std::max( ((int) radius), ((int) length) ) +1))
	{}

	const int getSize() const {return size;}

	VoxelType fn(const Box<VecType>& box) const
	{
		return relationToVoxel(cyl.testBox(box));
	}
private:
	const Cylinder<VecType> cyl;
	const int size;
};

///Moves a given constructor.
template<class VecType, class Con>
class MoveCon
{
public:
	MoveCon(const VecType& translation, const Con& constructor) :
		v(translation),
		c(constructor),
		size(AuxMath::log2(AuxMath::pow2(constructor.getSize()) + v.maxComponent()))
	{

	}

	const int getSize() const {return size;}

	const VoxelType fn(const Box<VecType>& box) const
	{
		Box<VecType> movedBox;
		movedBox.min=box.min-v;
		movedBox.max=box.max-v;
		return c.fn(movedBox);
	}
private:
	const VecType v;
	const Con c;
	const int size;
};

///Transforms a given constructor (move + rotation + scale).
#include "OgreMath/OgreMatrix4.h"
template<class Con>
class Transform3DCon
{
public:
	Transform3DCon(const Ogre::Vector3 &position, const Ogre::Vector3 &scale, const Ogre::Quaternion &orientation, const Con& constructor) :
		c(constructor),
		size(1+AuxMath::log2( AuxMath::pow2(constructor.getSize()*scale.maxComponent()) + position.maxComponent()))
	{
		transformInv.makeInverseTransform(position, scale, orientation);

	}
	const int getSize() const {return size;}

	const VoxelType fn(const Box<Vector3i>& box) const
	{
		Vector3i vBoxVertex=Vector3i(transformInv*(BoxTests<Vector3i>::getBoxVertex(box, 0).toOgreVec()));
		Box<Vector3i> transformedBox(vBoxVertex, vBoxVertex);

		if(box.min != box.max)
		{
			for (unsigned int i = 1; i < Vector3i::childCount; i++)
			{
				vBoxVertex=Vector3i(transformInv*(BoxTests<Vector3i>::getBoxVertex(box, i).toOgreVec()));
				for(unsigned int iDim=0; iDim<Vector3i::dimension; iDim++)
				{
					if (vBoxVertex.getComponent(iDim) < transformedBox.min.getComponent(iDim))
						transformedBox.min[iDim] = vBoxVertex.getComponent(iDim);
					else
					{
						if (vBoxVertex.getComponent(iDim) > transformedBox.max.getComponent(iDim))
							transformedBox.max[iDim] = vBoxVertex.getComponent(iDim);
					}
				}
			}
		}
		return c.fn(transformedBox);
	}
private:
	Ogre::Matrix4 transformInv;
	const Con c;
	const int size;
};

template<class Con>
class Transform2DCon
{
public:
	Transform2DCon(const Ogre::Vector2 &position, const Ogre::Vector2 &scale, const Ogre::Radian &rot, const Con& constructor) :
		c(constructor),
		size(1+AuxMath::log2( AuxMath::pow2(constructor.getSize()*scale.maxComponent()) + position.maxComponent()))
	{
		transformInv.makeInverseTransform(Ogre::Vector3(position.x, position.y, 0),
											Ogre::Vector3(scale.x, scale.y, 0),
											Ogre::Quaternion(rot, Ogre::Vector3(0,0,1)) );

	}
	const int getSize() const {return size;}

	const VoxelType fn(const Box<Vector2i>& box) const
	{
		Vector2i vFirstBoxVertex=transform2D(BoxTests<Vector2i>::getBoxVertex(box, 0));
		Box<Vector2i> transformedBox(vFirstBoxVertex, vFirstBoxVertex);

		for (unsigned int i = 1; i < Vector2i::childCount; i++)
		{
			Vector2i vBoxVertex=transform2D(BoxTests<Vector2i>::getBoxVertex(box, i));
			for(unsigned int iDim=0; iDim<Vector2i::dimension; iDim++)
			{
				if (vBoxVertex.getComponent(iDim) < transformedBox.min.getComponent(iDim)) transformedBox.min[iDim] = vBoxVertex.getComponent(iDim);
				if (transformedBox.max.getComponent(iDim) < vBoxVertex.getComponent(iDim)) transformedBox.max[iDim] = vBoxVertex.getComponent(iDim);
			}
		}
		return c.fn(transformedBox);
	}
private:
	Vector2i
	transform2D(const Vector2i& v) const
	{
		const Ogre::Vector3 vTransformed=transformInv*Ogre::Vector3(v.x, v.y, 0);
		return Vector2i(vTransformed.x, vTransformed.y);
	}

	Ogre::Matrix4 transformInv;
	const Con c;
	const int size;
};


//create a box with given extents
template<class VecType>
class BoxCon
{
public:
	BoxCon(const Box<VecType>& box) :
		halfExt(0), // unused
		refBox(box),
		size(AuxMath::log2(1 + std::max(box.min.maxComponent(), box.max.maxComponent())))
	{

	}

	BoxCon(const VecType& extents) :
		halfExt(extents.half()),
		refBox(Box<VecType>(halfExt.reverse(), extents-halfExt) ),
		size(1 + AuxMath::log2(halfExt.maxComponent()) )
	{

	}

	const int getSize() const {return size;}

	const VoxelType fn(const Box<VecType>& box) const
	{
		return relationToVoxel(BoxTests<VecType>::boxRelation(box, refBox));
	}
private:
	const VecType halfExt;
	const Box<VecType> refBox;
	const int size;
};

///Copies an existing tree.
template<class VecType, class DataType>
class CopyCon
{
public:
	CopyCon(const NTree<VecType, DataType>& t) : tree(t) {}
	const int getSize() const {return tree.getSize();}

	enum ERecurseResult
	{
		BOX_IS_SOLID,
		BOX_IS_EMPTY,
		BOX_IS_BOTH,
		BOX_NOT_HIT
	};

	//reduces BOX_NOT_HIT results
	static const ERecurseResult
	combine_results(ERecurseResult res[VecType::childCount])
	{
		int nEmpty=0,
			nSolid=0;
		for (unsigned int i = 0; i < VecType::childCount; i++)
		{
			switch(res[i])
			{
			case BOX_IS_SOLID:
				nSolid++;
				break;
			case BOX_IS_EMPTY:
				nEmpty++;
				break;
			case BOX_IS_BOTH:
				return BOX_IS_BOTH;
			default:
				break;
			}
		}
		if(!(nEmpty+nSolid))
			return BOX_NOT_HIT;
		if(!nEmpty)
			return BOX_IS_SOLID;
		if(!nSolid)
			return BOX_IS_EMPTY;
		return BOX_IS_BOTH;

	}

	static const ERecurseResult recurse_fn(const Box<VecType>& box,
			const typename NTree<VecType, DataType>::Node& node,
			const Area<VecType> &area)
	{
		VoxelType type=node.getType();

		Area<VecType> subAreas[VecType::childCount];
		RangeRelation rel=BoxTests<VecType>::boxRelation(box, area.toBox());
		if(rel==Outside)
			return BOX_NOT_HIT;

		switch(type)
		{
		case Solid:
			return BOX_IS_SOLID;
		case Nil:
			return BOX_IS_EMPTY;
		default:
			//must be both
			area.subAreas(subAreas);
			ERecurseResult res[VecType::childCount];
			for (unsigned int i = 0; i < VecType::childCount; i++)
				res[i]=recurse_fn(box, node.constGetSubNode(i), subAreas[i]);
			return combine_results(res);
		}
	}

	const VoxelType fn(const Box<VecType>& box) const
	{
		//VecType::neighborCount areas possible for an arbitrary box
		//simply recurse into tree
		Area<VecType> subs[VecType::childCount];
		tree.subAreas(subs);

		ERecurseResult res[VecType::childCount];
		for(unsigned int i=0; i<VecType::childCount; i++)
			res[i]=recurse_fn(box, tree.constGetRoot(i), subs[i]);
		ERecurseResult combined=combine_results(res);
		switch(combined)
		{
		case BOX_IS_SOLID:
			return Solid;
		case BOX_IS_BOTH:
			return Both;
		case BOX_IS_EMPTY:
		default:
			return Nil;
		}
	}
private:
	const NTree<VecType, DataType>& tree;
};
